﻿using Core.Authorization;
using Core.Persistence;
using Core.SystemLogs;
using Core.Tenant.Abstractions;
using Finbuckle.MultiTenant;
using Finbuckle.MultiTenant.Abstractions;
using Infrastructure.Tenant;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace BP.Plugin.Monitor.Infrastructure;

/// <summary>
/// 自动同步日志到租户数据库
/// </summary>
public class TenantLogSyncService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly TimeSpan _interval = TimeSpan.FromSeconds(10); // 每 60 秒同步一次

    public TenantLogSyncService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await SyncLogsToTenantsAsync(stoppingToken);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"租户日志同步失败: {ex}");
            }

            await Task.Delay(_interval, stoppingToken);
        }
    }

    private async Task SyncLogsToTenantsAsync(CancellationToken cancellationToken)
    {
        using var scope = _serviceProvider.CreateScope();
        var tenantService = scope.ServiceProvider.GetRequiredService<ITenantService>();
        var allTenants = await tenantService.GetAllAsync();

        var mainTenant = allTenants.First(x => x.Id == TenantConstants.Root.Id);
        var mainContextSetter = scope.ServiceProvider.GetRequiredService<IMultiTenantContextSetter>();
        mainContextSetter.MultiTenantContext = new MultiTenantContext<BoilerplateTenantInfo>()
        {
            TenantInfo = mainTenant.Adapt<BoilerplateTenantInfo>()
        };
        foreach (var tenant in allTenants)
        {
            if (string.IsNullOrWhiteSpace(tenant.ConnectionString))//主库租户不需要同步日志
                continue;

            var mainLogDb = scope.ServiceProvider.GetRequiredService<ILogDbContext>();

            // ① 仅从主库查询未同步的日志
            var unsyncedLogs = await mainLogDb.SystemLogs
                .IgnoreQueryFilters()
                .Where(l => !l.Synced && EF.Property<string>(l, "TenantId") == tenant.Id)
                .OrderBy(l => l.Id)
                .AsNoTracking()
                .ToListAsync(cancellationToken);

            if (unsyncedLogs.Count == 0) 
                return;

            // ② 生成插入租户库的日志
            var newLogs = unsyncedLogs.Select(l => new SystemLog
            {
                MainLogId = l.Id,
                TimeStamp = l.TimeStamp,
                Level = l.Level,
                Message = l.Message,
                MessageTemplate = l.MessageTemplate,
                Exception = l.Exception,
                UserName = l.UserName,
                ClientIP = l.ClientIP,
                ClientAgent = l.ClientAgent,
                Properties = l.Properties,
                LogEvent = l.LogEvent,
                Module = l.Module,
                Operation = l.Operation,
                Route = l.Route,
                HttpMethod = l.HttpMethod,
                OS = l.OS,
                Browser = l.Browser,
                ElapsedMilliseconds = l.ElapsedMilliseconds,
                Address = l.Address,
                StatusCode = l.StatusCode,
                Headers = l.Headers,
                Synced = true
            }).ToList();

            using var tenantScope = _serviceProvider.CreateScope();
            var tenantSetter = tenantScope.ServiceProvider.GetRequiredService<IMultiTenantContextSetter>();
            tenantSetter.MultiTenantContext = new MultiTenantContext<BoilerplateTenantInfo>()
            {
                TenantInfo = tenant.Adapt<BoilerplateTenantInfo>()
            };

            var tenantLogDb = tenantScope.ServiceProvider.GetRequiredService<ILogDbContext>();
            await tenantLogDb.SystemLogs.AddRangeAsync(newLogs, cancellationToken);
            await tenantLogDb.SaveChangesAsync(cancellationToken);

            await mainLogDb.SystemLogs
                .IgnoreQueryFilters()
                .Where(l => EF.Property<string>(l, "TenantId") == tenant.Id && !l.Synced)
                .ExecuteUpdateAsync(setters => setters
                .SetProperty(l => l.Synced, true), cancellationToken);
        }
    }
}
